library(Seurat)
library(SnapATAC)
library(tidyverse)
library(DescTools)  # 4 AUC function
Registered S3 method overwritten by 'DescTools':
  method         from 
  reorder.factor gdata
library(glue)

Attaching package: ‘glue’

The following object is masked from ‘package:SummarizedExperiment’:

    trim

The following object is masked from ‘package:GenomicRanges’:

    trim

The following objects are masked from ‘package:IRanges’:

    collapse, trim

The following object is masked from ‘package:dplyr’:

    collapse
library(ggalluvial)  # 4 river plot
library(ggpubr)
model.cca <- readRDS("~/models/modelCCA_union_hvg_F74_SCElist_20191113.RDS")
model.liger <- readRDS("~/models/modelLiger_union_hvg_F74_SCElist_20191113.RDS")
model.conos <- readRDS("~/models/modelConos_union_hvg_F74_SCElist_20191113.RDS")
seu.cca <- readRDS("~/models/labelTransferCCA_union_hvg_F74_SCElist_20191113.RDS")
seu.liger <- readRDS("~/models/labelTransferLiger_union_hvg_F74_SCElist_20191113.RDS")
seu.conos <- readRDS("~/models/labelTransferConos_union_hvg_F74_SCElist_20191113.RDS")
integrate_features <- scan("~/intFeatures_union_hvg_2000_F74_SCElist_20191113.txt", what='')
Read 3029 items
int.list <- list(CCA=seu.cca, Liger=seu.liger, Conos=seu.conos)
# ## Make method color palette
# method.palette <- brewer_palette_4_values(names(int.list), "Set1")

Embeddings

Visualize label transfer on original ATAC data (embedded SnapATAC bins)

## Load original data
orig.ATAC <- readRDS("~/my_data/cellranger-atac110_count_30439_WSSS8038360_GRCh38-1_1_0.snapATAC.RDS")
sce.list <- readRDS("~/my_data/integrated_thymus/F74_SCElist_20191113.RDS")
orig.RNA <- sce.list$RNA
## Make SeuratObjects
atac.seu <- snapToSeurat(
    obj=orig.ATAC, 
    eigs.dims=1:20, 
    norm=TRUE,
    scale=TRUE
    )
Epoch: checking input parameters ... 
Non-unique features (rownames) present in the input matrix, making uniquePerforming log-normalization
0%   10   20   30   40   50   60   70   80   90   100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
Centering and scaling data matrix

  |                                                                              
  |                                                                        |   0%
  |                                                                              
  |==                                                                      |   3%
  |                                                                              
  |====                                                                    |   6%
  |                                                                              
  |======                                                                  |   9%
  |                                                                              
  |========                                                                |  12%
  |                                                                              
  |===========                                                             |  15%
  |                                                                              
  |=============                                                           |  18%
  |                                                                              
  |===============                                                         |  21%
  |                                                                              
  |=================                                                       |  24%
  |                                                                              
  |===================                                                     |  26%
  |                                                                              
  |=====================                                                   |  29%
  |                                                                              
  |=======================                                                 |  32%
  |                                                                              
  |=========================                                               |  35%
  |                                                                              
  |============================                                            |  38%
  |                                                                              
  |==============================                                          |  41%
  |                                                                              
  |================================                                        |  44%
  |                                                                              
  |==================================                                      |  47%
  |                                                                              
  |====================================                                    |  50%
  |                                                                              
  |======================================                                  |  53%
  |                                                                              
  |========================================                                |  56%
  |                                                                              
  |==========================================                              |  59%
  |                                                                              
  |============================================                            |  62%
  |                                                                              
  |===============================================                         |  65%
  |                                                                              
  |=================================================                       |  68%
  |                                                                              
  |===================================================                     |  71%
  |                                                                              
  |=====================================================                   |  74%
  |                                                                              
  |=======================================================                 |  76%
  |                                                                              
  |=========================================================               |  79%
  |                                                                              
  |===========================================================             |  82%
  |                                                                              
  |=============================================================           |  85%
  |                                                                              
  |================================================================        |  88%
  |                                                                              
  |==================================================================      |  91%
  |                                                                              
  |====================================================================    |  94%
  |                                                                              
  |======================================================================  |  97%
  |                                                                              
  |========================================================================| 100%
atac.seu <- RenameCells(atac.seu, new.names = orig.ATAC@metaData$barcode)
## Add cell type predictions
getPredictedLabels <- function(seu.int, int.name, id.col="predicted.id", score.col="score"){
  pred.df <- seu.int$ATAC@meta.data[,c(id.col, score.col), drop=F] 
  rownames(pred.df) <- str_remove(rownames(pred.df), "^ATAC_")
  colnames(pred.df) <- c(str_c("predicted.id", "_", int.name), str_c("score", "_", int.name))
  pred.df
  }
pred.cca <- getPredictedLabels(seu.cca, "CCA", score.col = "prediction.score.max")
pred.liger <- getPredictedLabels(seu.liger, "Liger")
pred.conos <- getPredictedLabels(seu.conos, "Conos")
if (all(rownames(pred.conos) == rownames(pred.cca)) & all(rownames(pred.conos) == rownames(pred.liger))) {
  atac.seu <- AddMetaData(atac.seu, metadata = cbind(pred.cca, pred.liger, pred.conos))
} else {
  stop("Non corresponding cell names")
}

plotly::ggplotly(pl)
geom_GeomTextRepel() has yet to be implemented in plotly.
  If you'd like to see this geom implemented,
  Please open an issue with your example code at
  https://github.com/ropensci/plotly/issues

Prediction score

Quantifies the uncertainty of the prediction. Calculated differently for every method, but used to define which cells are “unassigned”.

orig.composition <- orig.RNA$annotation
orig.frac <- table(orig.composition)/length(orig.composition)
orig.frac.df <- data.frame(orig.frac) %>%
  dplyr::rename(predicted.id=orig.composition, frac.label=Freq) %>%
  mutate(method="original.RNA")
score_cols <- str_subset(colnames(atac.seu@meta.data), 'score_')
label_cols <- str_subset(colnames(atac.seu@meta.data), 'predicted.id_')
pred.labels.df <- imap(list(CCA=pred.cca, Liger=pred.liger, Conos=pred.conos), ~ 
      rownames_to_column(.x, "cell") %>%
      rename_all(funs(str_remove(., str_c("_",.y)))) %>%
      mutate(method=.y)
    ) %>%
  purrr::reduce(bind_rows) %>%
  mutate(score=ifelse(is.na(score), 0, score))
funs() is soft deprecated as of dplyr 0.8.0
Please use a list of either functions or lambdas: 

  # Simple named list: 
  list(mean = mean, median = median)

  # Auto named with `tibble::lst()`: 
  tibble::lst(mean, median)

  # Using lambdas
  list(~ mean(., trim = .2), ~ median(., na.rm = TRUE))
This warning is displayed once per session.binding character and factor vector, coercing into character vector
predict_score_hist <- 
  pred.labels.df %>%
  ggplot(aes(score, fill=method)) +
  geom_histogram(position="identity", alpha=0.8, bins=40) +
  facet_grid(method ~.) +
  scale_fill_brewer(palette="Set1") +
  xlab("Label prediction score") +
  theme_bw(base_size = 16) +
  theme(legend.position = "top")
cutoffs <- seq(0,1,0.05)
predict_score_cumedist <-
  pred.labels.df %>%
  group_by(method) %>%
  mutate(bins=cut(score, breaks = cutoffs)) %>%
  mutate(score=as.numeric(str_remove_all(as.character(bins), ".+,|]"))) %>%
  ggplot(aes(score, color=method)) +
  stat_ecdf(size=0.8, alpha=0.7) +
  scale_color_brewer(palette = "Set1") +
  ylab("Fraction of unassigned cells") +
  xlab("Prediction score cutoff") +
  theme_bw(base_size = 16) +
  xlim(0,1) +
  coord_fixed() +
  guides(color="none") 
ggpubr::ggarrange(predict_score_hist, predict_score_cumedist, common.legend = TRUE, widths = c(0.8, 1.2),
          labels=c("A", "B")) +
  ggsave(paste0(outdir, "prediction_score_distribution.png"), height = 6, width = 10)
Removed 63 rows containing non-finite values (stat_ecdf).

ggpubr::ggarrange(
  plotlist = list(
    FeaturePlot(atac.seu, reduction = "umap.snap", feature = "score_CCA"  , coord.fixed = TRUE) + ggtitle("CCA"),
    FeaturePlot(atac.seu, reduction = "umap.snap", feature = "score_Liger", coord.fixed = TRUE) + ggtitle("Liger"),
    FeaturePlot(atac.seu, reduction = "umap.snap", feature = "score_Conos", coord.fixed = TRUE) + ggtitle("Conos")
  ),
  common.legend = TRUE, ncol=3, nrow=1
) +
  ggsave(paste0(outdir, "prediction_score_umaps.png"), height = 7, width=14)

Cell type composition

Compare cell type fractions (w uncertainty)

pred.labels.df %>%
  group_by(method) %>%
  drop_na() %>%
  mutate(tot.cells=n()) %>%
  ungroup() %>%
  group_by(method, predicted.id) %>%
  summarise(tot.label = n(), tot.cells = max(tot.cells), mean.score=mean(score)) %>%
  mutate(frac.label=tot.label/tot.cells) %>%
  bind_rows(orig.frac.df) %>%
  mutate(orig.rank = orig.rank.df[predicted.id,]) %>%
  mutate(predicted.id=factor(predicted.id, levels=rownames(orig.rank.df)))%>%
  # select(method, predicted.id, frac.label) %>%
  # distinct() %>%
  ggplot(aes(predicted.id, frac.label, fill=mean.score, color=mean.score)) +
  geom_point(size=2) +
  geom_col(width=0.05) +
  coord_flip() +
  # geom_line(aes(group=method)) +
  facet_wrap(method~., nrow=1, ncol=4, scales="free_x") +
  scale_color_viridis_c() +
  scale_fill_viridis_c() +
  ylab("Fraction of cells") +
  theme_bw(base_size = 16) +
  ggsave(paste0(outdir, "cell_type_composition_bars.png"), width = 15, height = 7)
binding character and factor vector, coercing into character vector

Agreement with unsupervised clustering of ATAC data

Calculate which fractions of NNs in bin based graph of ATAC cells have the same annotation

k = 30
atac.seu <- FindNeighbors(atac.seu, assay = "ATAC", reduction = "SnapATAC", dims = 1:15, k.param = k)
Computing nearest neighbor graph
Computing SNN
atac.nn.list <- getNNlist(atac.seu)
score.CCA <- imap_dbl(atac.nn.list, ~ sum(pred.cca[.x,1] == pred.cca[.y,1])/k) %>% setNames(names(atac.nn.list))
score.Conos <- imap_dbl(atac.nn.list, ~ sum(pred.conos[.x,1] == pred.conos[.y,1])/k) %>% setNames(names(atac.nn.list))
score.Liger <- imap_dbl(atac.nn.list, ~ sum(pred.liger[.x,1] == pred.liger[.y,1])/k) %>% setNames(names(atac.nn.list))
knn_score_df <-
  as.data.frame(cbind(score.Conos, score.Liger, score.CCA)) %>%
  rownames_to_column("cell") %>%
  pivot_longer(cols=str_subset(colnames(.), "score"), names_to = "method", values_to = "KNN_score") %>%
  dplyr::mutate(KNN_score=ifelse(is.na(KNN_score), 0, KNN_score),
                method=str_remove(method, "score."))
quants = seq(0,1, by = 0.05)
AUECDF_knn_score <- knn_score_df %>%
  split(.$method) %>%
  map_dbl( ~ .x %>%
      arrange(KNN_score) %>% 
      {ecdf(.$KNN_score)(quants)} %>% AUC(quants,.)
    )
  
knn_score_df %>%
  mutate(AUC=AUECDF_knn_score[method]) %>%
  ggplot(aes(KNN_score, color=method, fill=method)) +
  stat_ecdf(size=1) +
  scale_color_brewer(palette = "Set1") +
  geom_text(data=. %>% group_by(method) %>% summarise(AUC=max(AUC)), 
            x=0.05, hjust=0,
            aes(label=glue("AUC = {round(AUC, 3)}"), y=c(0.90, 0.95, 1))) +
  theme_bw(base_size = 16) +
  ylab("ECDF") +
  ggsave(paste(outdir,"KNN_score_ecdf_unionHVG.png"), height = 4, width=6)

full_join(pred.labels.df, knn_score_df) %>%
  ggplot(aes(KNN_score, color=method)) +
  stat_ecdf() +
  facet_wrap("predicted.id") +
  scale_color_brewer(palette = "Set1") +
  coord_fixed()
Joining, by = c("cell", "method")

Accessibility of markers

Taking markers from Fig. S2 of JP’s manuscript

Reproducing Fig.2H on T-cell development

tcells.markers.df %>%
  full_join(t.cell.markers.df) %>%
  # filter(method=="CCA") %>%
  mutate(predicted.id=factor(predicted.id, levels=ordered.tcells)) %>%
  ggplot(aes( predicted.id, gene)) +
  facet_grid(cell.type.class~method, scales = "free_y", space="free") +
  geom_point(aes(size=frac.cells, color=mean.acc)) +
  scale_color_gradient(high="darkblue", low="white") +
  # scale_color_gradient2(midpoint = 0.5) +
  theme_bw(base_size = 16) +
  theme(axis.text.x = element_text(angle=90, hjust=1, vjust=0.5),
        strip.text.y = element_text(angle=0)) 
Joining, by = "gene"
Column `gene` joining factor and character vector, coercing into character vector

Thoughts

  • Conos scores a lot of cells with high confidence, but fails to assign cells to difficult clusters
  • CCA resembles the composition of the RNA data better, but curious that the other methods identify way more
LS0tCnRpdGxlOiAiTGFiZWwgdHJhbnNmZXIgRURBIgpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sKLS0tCgoKYGBge3J9CmxpYnJhcnkoU2V1cmF0KQpsaWJyYXJ5KFNuYXBBVEFDKQpsaWJyYXJ5KHRpZHl2ZXJzZSkKbGlicmFyeShEZXNjVG9vbHMpICAjIDQgQVVDIGZ1bmN0aW9uCmxpYnJhcnkoZ2x1ZSkKbGlicmFyeShnZ2FsbHV2aWFsKSAgIyA0IHJpdmVyIHBsb3QKbGlicmFyeShnZ3B1YnIpCnNvdXJjZSgifi9tdWx0aU9taWNfYmVuY2htYXJrL3V0aWxzLlIiKQoKCmdnX2NvbG9yX2h1ZSA8LSBmdW5jdGlvbihuKSB7CiAgaHVlcyA9IHNlcSgxNSwgMzc1LCBsZW5ndGggPSBuICsgMSkKICBoY2woaCA9IGh1ZXMsIGwgPSA2NSwgYyA9IDEwMClbMTpuXQp9CgojIyBNYWtlIG91dHB1dCBkaXJlY3RvcnkKb3V0ZGlyIDwtICJ+L211bHRpT21pY19iZW5jaG1hcmsvcmVwb3J0L291dHB1dC8yMDE5MTExM19sYWJlbFRyYW5zZmVyRURBX0Y3NF92Mi8iCmlmZWxzZSghZGlyLmV4aXN0cyhvdXRkaXIpLCBkaXIuY3JlYXRlKG91dGRpciksIEZBTFNFKQpgYGAKCgpgYGB7cn0KbW9kZWwuY2NhIDwtIHJlYWRSRFMoIn4vbW9kZWxzL21vZGVsQ0NBX3VuaW9uX2h2Z19GNzRfU0NFbGlzdF8yMDE5MTExMy5SRFMiKQptb2RlbC5saWdlciA8LSByZWFkUkRTKCJ+L21vZGVscy9tb2RlbExpZ2VyX3VuaW9uX2h2Z19GNzRfU0NFbGlzdF8yMDE5MTExMy5SRFMiKQptb2RlbC5jb25vcyA8LSByZWFkUkRTKCJ+L21vZGVscy9tb2RlbENvbm9zX3VuaW9uX2h2Z19GNzRfU0NFbGlzdF8yMDE5MTExMy5SRFMiKQoKc2V1LmNjYSA8LSByZWFkUkRTKCJ+L21vZGVscy9sYWJlbFRyYW5zZmVyQ0NBX3VuaW9uX2h2Z19GNzRfU0NFbGlzdF8yMDE5MTExMy5SRFMiKQpzZXUubGlnZXIgPC0gcmVhZFJEUygifi9tb2RlbHMvbGFiZWxUcmFuc2ZlckxpZ2VyX3VuaW9uX2h2Z19GNzRfU0NFbGlzdF8yMDE5MTExMy5SRFMiKQpzZXUuY29ub3MgPC0gcmVhZFJEUygifi9tb2RlbHMvbGFiZWxUcmFuc2ZlckNvbm9zX3VuaW9uX2h2Z19GNzRfU0NFbGlzdF8yMDE5MTExMy5SRFMiKQoKCmludGVncmF0ZV9mZWF0dXJlcyA8LSBzY2FuKCJ+L2ludEZlYXR1cmVzX3VuaW9uX2h2Z18yMDAwX0Y3NF9TQ0VsaXN0XzIwMTkxMTEzLnR4dCIsIHdoYXQ9JycpCgppbnQubGlzdCA8LSBsaXN0KENDQT1zZXUuY2NhLCBMaWdlcj1zZXUubGlnZXIsIENvbm9zPXNldS5jb25vcykKCiMgIyMgTWFrZSBtZXRob2QgY29sb3IgcGFsZXR0ZQojIG1ldGhvZC5wYWxldHRlIDwtIGJyZXdlcl9wYWxldHRlXzRfdmFsdWVzKG5hbWVzKGludC5saXN0KSwgIlNldDEiKQoKYGBgCgojIyMgRW1iZWRkaW5ncwpWaXN1YWxpemUgbGFiZWwgdHJhbnNmZXIgb24gb3JpZ2luYWwgQVRBQyBkYXRhIChlbWJlZGRlZCBTbmFwQVRBQyBiaW5zKQpgYGB7cn0KIyMgTG9hZCBvcmlnaW5hbCBkYXRhCm9yaWcuQVRBQyA8LSByZWFkUkRTKCJ+L215X2RhdGEvY2VsbHJhbmdlci1hdGFjMTEwX2NvdW50XzMwNDM5X1dTU1M4MDM4MzYwX0dSQ2gzOC0xXzFfMC5zbmFwQVRBQy5SRFMiKQpzY2UubGlzdCA8LSByZWFkUkRTKCJ+L215X2RhdGEvaW50ZWdyYXRlZF90aHltdXMvRjc0X1NDRWxpc3RfMjAxOTExMTMuUkRTIikKb3JpZy5STkEgPC0gc2NlLmxpc3QkUk5BCgojIyBNYWtlIFNldXJhdE9iamVjdHMKYXRhYy5zZXUgPC0gc25hcFRvU2V1cmF0KAogICAgb2JqPW9yaWcuQVRBQywgCiAgICBlaWdzLmRpbXM9MToyMCwgCiAgICBub3JtPVRSVUUsCiAgICBzY2FsZT1UUlVFCiAgICApCmF0YWMuc2V1IDwtIFJlbmFtZUNlbGxzKGF0YWMuc2V1LCBuZXcubmFtZXMgPSBvcmlnLkFUQUNAbWV0YURhdGEkYmFyY29kZSkKCiMjIEFkZCBjZWxsIHR5cGUgcHJlZGljdGlvbnMKZ2V0UHJlZGljdGVkTGFiZWxzIDwtIGZ1bmN0aW9uKHNldS5pbnQsIGludC5uYW1lLCBpZC5jb2w9InByZWRpY3RlZC5pZCIsIHNjb3JlLmNvbD0ic2NvcmUiKXsKICBwcmVkLmRmIDwtIHNldS5pbnQkQVRBQ0BtZXRhLmRhdGFbLGMoaWQuY29sLCBzY29yZS5jb2wpLCBkcm9wPUZdIAogIHJvd25hbWVzKHByZWQuZGYpIDwtIHN0cl9yZW1vdmUocm93bmFtZXMocHJlZC5kZiksICJeQVRBQ18iKQogIGNvbG5hbWVzKHByZWQuZGYpIDwtIGMoc3RyX2MoInByZWRpY3RlZC5pZCIsICJfIiwgaW50Lm5hbWUpLCBzdHJfYygic2NvcmUiLCAiXyIsIGludC5uYW1lKSkKICBwcmVkLmRmCiAgfQoKcHJlZC5jY2EgPC0gZ2V0UHJlZGljdGVkTGFiZWxzKHNldS5jY2EsICJDQ0EiLCBzY29yZS5jb2wgPSAicHJlZGljdGlvbi5zY29yZS5tYXgiKQpwcmVkLmxpZ2VyIDwtIGdldFByZWRpY3RlZExhYmVscyhzZXUubGlnZXIsICJMaWdlciIpCnByZWQuY29ub3MgPC0gZ2V0UHJlZGljdGVkTGFiZWxzKHNldS5jb25vcywgIkNvbm9zIikKCmlmIChhbGwocm93bmFtZXMocHJlZC5jb25vcykgPT0gcm93bmFtZXMocHJlZC5jY2EpKSAmIGFsbChyb3duYW1lcyhwcmVkLmNvbm9zKSA9PSByb3duYW1lcyhwcmVkLmxpZ2VyKSkpIHsKICBhdGFjLnNldSA8LSBBZGRNZXRhRGF0YShhdGFjLnNldSwgbWV0YWRhdGEgPSBjYmluZChwcmVkLmNjYSwgcHJlZC5saWdlciwgcHJlZC5jb25vcykpCn0gZWxzZSB7CiAgc3RvcCgiTm9uIGNvcnJlc3BvbmRpbmcgY2VsbCBuYW1lcyIpCn0KYGBgCgpgYGB7ciwgZmlnLmhlaWdodD04LCBmaWcud2lkdGg9MTh9CiMjIG1ha2UgY2VsbCB0eXBlIHBhbGV0dGUKY2VsbC50eXBlcyA8LSBsZXZlbHMoc2V1LmNjYSRSTkEkYW5ub3RhdGlvbikKY2VsbC50eXBlLnBhbCA8LSBzZXROYW1lcyhzYW1wbGUoZ2dfY29sb3JfaHVlKGxlbmd0aChjZWxsLnR5cGVzKSApKSwgY2VsbC50eXBlcykKCmF0YWMuc2V1IDwtIFJ1blVNQVAoYXRhYy5zZXUsIHJlZHVjdGlvbiA9ICJTbmFwQVRBQyIsIHJlZHVjdGlvbi5uYW1lID0gInVtYXAuc25hcCIsIGRpbXM9MToyMCkKCmdncHVicjo6Z2dhcnJhbmdlKAogIHBsb3RsaXN0ID0gbGlzdCgKICAgIERpbVBsb3QoYXRhYy5zZXUsIHJlZHVjdGlvbiA9ICJ1bWFwLnNuYXAiLCBncm91cC5ieSA9ICJwcmVkaWN0ZWQuaWRfQ0NBIiAgLCBjb2xzPWNlbGwudHlwZS5wYWwsIGxhYmVsPVRSVUUsIHJlcGVsPVRSVUUpICsgZ2d0aXRsZSgiQ0NBIiksCiAgICBEaW1QbG90KGF0YWMuc2V1LCByZWR1Y3Rpb24gPSAidW1hcC5zbmFwIiwgZ3JvdXAuYnkgPSAicHJlZGljdGVkLmlkX0xpZ2VyIiwgY29scz1jZWxsLnR5cGUucGFsLCBsYWJlbD1UUlVFLCByZXBlbD1UUlVFKSArIGdndGl0bGUoIkxpZ2VyIiksCiAgICBEaW1QbG90KGF0YWMuc2V1LCByZWR1Y3Rpb24gPSAidW1hcC5zbmFwIiwgZ3JvdXAuYnkgPSAicHJlZGljdGVkLmlkX0Nvbm9zIiwgY29scz1jZWxsLnR5cGUucGFsLCBsYWJlbD1UUlVFLCByZXBlbD1UUlVFKSArIGdndGl0bGUoIkNvbm9zIikKICApLAogIGNvbW1vbi5sZWdlbmQgPSBUUlVFLCBuY29sPTMsIG5yb3c9MQopICsKICBnZ3NhdmUocGFzdGUwKG91dGRpciwgInVtYXBfbGFiZWxzLnBuZyIpLCB3aWR0aD0xNiwgaGVpZ2h0ID0gOCkKCgpgYGAKCmBgYHtyfQpwbCA8LSAgICAgRGltUGxvdChhdGFjLnNldSwgcmVkdWN0aW9uID0gInVtYXAuc25hcCIsIGdyb3VwLmJ5ID0gInByZWRpY3RlZC5pZF9DQ0EiLCBjb2xzPWNlbGwudHlwZS5wYWwsIGxhYmVsPVRSVUUsIHJlcGVsPVRSVUUpICsgZ2d0aXRsZSgiQ0NBIikKcGxvdGx5OjpnZ3Bsb3RseShwbCkKYGBgCgoKYGBge3J9Cm9yaWcuUk5BLnNldSA8LSBhcy5TZXVyYXQob3JpZy5STkEpCm9yaWcuUk5BLnNldSA8LSBGaW5kVmFyaWFibGVGZWF0dXJlcyhvcmlnLlJOQS5zZXUpCm9yaWcuUk5BLnNldSA8LSBTY2FsZURhdGEob3JpZy5STkEuc2V1KQpvcmlnLlJOQS5zZXUgPC0gUnVuUENBKG9yaWcuUk5BLnNldSkKb3JpZy5STkEuc2V1IDwtIFJ1blVNQVAob3JpZy5STkEuc2V1LCBkaW1zPTE6NDApCgpwbG90bHk6OmdncGxvdGx5KERpbVBsb3Qob3JpZy5STkEuc2V1LCBncm91cC5ieT0iYW5ub3RhdGlvbiIpKQpgYGAKCiMjIFByZWRpY3Rpb24gc2NvcmUKUXVhbnRpZmllcyB0aGUgdW5jZXJ0YWludHkgb2YgdGhlIHByZWRpY3Rpb24uIENhbGN1bGF0ZWQgZGlmZmVyZW50bHkgZm9yIGV2ZXJ5IG1ldGhvZCwgYnV0IHVzZWQgdG8gZGVmaW5lIHdoaWNoIGNlbGxzIGFyZSAidW5hc3NpZ25lZCIuCgoKYGBge3J9Cm9yaWcuY29tcG9zaXRpb24gPC0gb3JpZy5STkEkYW5ub3RhdGlvbgpvcmlnLmZyYWMgPC0gdGFibGUob3JpZy5jb21wb3NpdGlvbikvbGVuZ3RoKG9yaWcuY29tcG9zaXRpb24pCgpvcmlnLmZyYWMuZGYgPC0gZGF0YS5mcmFtZShvcmlnLmZyYWMpICU+JQogIGRwbHlyOjpyZW5hbWUocHJlZGljdGVkLmlkPW9yaWcuY29tcG9zaXRpb24sIGZyYWMubGFiZWw9RnJlcSkgJT4lCiAgbXV0YXRlKG1ldGhvZD0ib3JpZ2luYWwuUk5BIikKCnNjb3JlX2NvbHMgPC0gc3RyX3N1YnNldChjb2xuYW1lcyhhdGFjLnNldUBtZXRhLmRhdGEpLCAnc2NvcmVfJykKbGFiZWxfY29scyA8LSBzdHJfc3Vic2V0KGNvbG5hbWVzKGF0YWMuc2V1QG1ldGEuZGF0YSksICdwcmVkaWN0ZWQuaWRfJykKCnByZWQubGFiZWxzLmRmIDwtIGltYXAobGlzdChDQ0E9cHJlZC5jY2EsIExpZ2VyPXByZWQubGlnZXIsIENvbm9zPXByZWQuY29ub3MpLCB+IAogICAgICByb3duYW1lc190b19jb2x1bW4oLngsICJjZWxsIikgJT4lCiAgICAgIHJlbmFtZV9hbGwoZnVucyhzdHJfcmVtb3ZlKC4sIHN0cl9jKCJfIiwueSkpKSkgJT4lCiAgICAgIG11dGF0ZShtZXRob2Q9LnkpCiAgICApICU+JQogIHB1cnJyOjpyZWR1Y2UoYmluZF9yb3dzKSAlPiUKICBtdXRhdGUoc2NvcmU9aWZlbHNlKGlzLm5hKHNjb3JlKSwgMCwgc2NvcmUpKQoKcHJlZGljdF9zY29yZV9oaXN0IDwtIAogIHByZWQubGFiZWxzLmRmICU+JQogIGdncGxvdChhZXMoc2NvcmUsIGZpbGw9bWV0aG9kKSkgKwogIGdlb21faGlzdG9ncmFtKHBvc2l0aW9uPSJpZGVudGl0eSIsIGFscGhhPTAuOCwgYmlucz00MCkgKwogIGZhY2V0X2dyaWQobWV0aG9kIH4uKSArCiAgc2NhbGVfZmlsbF9icmV3ZXIocGFsZXR0ZT0iU2V0MSIpICsKICB4bGFiKCJMYWJlbCBwcmVkaWN0aW9uIHNjb3JlIikgKwogIHRoZW1lX2J3KGJhc2Vfc2l6ZSA9IDE2KSArCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gInRvcCIpCgpjdXRvZmZzIDwtIHNlcSgwLDEsMC4wNSkKcHJlZGljdF9zY29yZV9jdW1lZGlzdCA8LQogIHByZWQubGFiZWxzLmRmICU+JQogIGdyb3VwX2J5KG1ldGhvZCkgJT4lCiAgbXV0YXRlKGJpbnM9Y3V0KHNjb3JlLCBicmVha3MgPSBjdXRvZmZzKSkgJT4lCiAgbXV0YXRlKHNjb3JlPWFzLm51bWVyaWMoc3RyX3JlbW92ZV9hbGwoYXMuY2hhcmFjdGVyKGJpbnMpLCAiLissfF0iKSkpICU+JQogIGdncGxvdChhZXMoc2NvcmUsIGNvbG9yPW1ldGhvZCkpICsKICBzdGF0X2VjZGYoc2l6ZT0wLjgsIGFscGhhPTAuNykgKwogIHNjYWxlX2NvbG9yX2JyZXdlcihwYWxldHRlID0gIlNldDEiKSArCiAgeWxhYigiRnJhY3Rpb24gb2YgdW5hc3NpZ25lZCBjZWxscyIpICsKICB4bGFiKCJQcmVkaWN0aW9uIHNjb3JlIGN1dG9mZiIpICsKICB0aGVtZV9idyhiYXNlX3NpemUgPSAxNikgKwogIHhsaW0oMCwxKSArCiAgY29vcmRfZml4ZWQoKSArCiAgZ3VpZGVzKGNvbG9yPSJub25lIikgCgpnZ3B1YnI6OmdnYXJyYW5nZShwcmVkaWN0X3Njb3JlX2hpc3QsIHByZWRpY3Rfc2NvcmVfY3VtZWRpc3QsIGNvbW1vbi5sZWdlbmQgPSBUUlVFLCB3aWR0aHMgPSBjKDAuOCwgMS4yKSwKICAgICAgICAgIGxhYmVscz1jKCJBIiwgIkIiKSkgKwogIGdnc2F2ZShwYXN0ZTAob3V0ZGlyLCAicHJlZGljdGlvbl9zY29yZV9kaXN0cmlidXRpb24ucG5nIiksIGhlaWdodCA9IDYsIHdpZHRoID0gMTApCmBgYAoKYGBge3IsIGZpZy53aWR0aD0xNiwgZmlnLmhlaWdodD04fQpnZ3B1YnI6OmdnYXJyYW5nZSgKICBwbG90bGlzdCA9IGxpc3QoCiAgICBGZWF0dXJlUGxvdChhdGFjLnNldSwgcmVkdWN0aW9uID0gInVtYXAuc25hcCIsIGZlYXR1cmUgPSAic2NvcmVfQ0NBIiAgLCBjb29yZC5maXhlZCA9IFRSVUUpICsgZ2d0aXRsZSgiQ0NBIiksCiAgICBGZWF0dXJlUGxvdChhdGFjLnNldSwgcmVkdWN0aW9uID0gInVtYXAuc25hcCIsIGZlYXR1cmUgPSAic2NvcmVfTGlnZXIiLCBjb29yZC5maXhlZCA9IFRSVUUpICsgZ2d0aXRsZSgiTGlnZXIiKSwKICAgIEZlYXR1cmVQbG90KGF0YWMuc2V1LCByZWR1Y3Rpb24gPSAidW1hcC5zbmFwIiwgZmVhdHVyZSA9ICJzY29yZV9Db25vcyIsIGNvb3JkLmZpeGVkID0gVFJVRSkgKyBnZ3RpdGxlKCJDb25vcyIpCiAgKSwKICBjb21tb24ubGVnZW5kID0gVFJVRSwgbmNvbD0zLCBucm93PTEKKSArCiAgZ2dzYXZlKHBhc3RlMChvdXRkaXIsICJwcmVkaWN0aW9uX3Njb3JlX3VtYXBzLnBuZyIpLCBoZWlnaHQgPSA3LCB3aWR0aD0xNCkKYGBgCgoKIyMgQ2VsbCB0eXBlIGNvbXBvc2l0aW9uCgpDb21wYXJlIGNlbGwgdHlwZSBmcmFjdGlvbnMgKHcgdW5jZXJ0YWludHkpCgpgYGB7ciwgZmlnLndpZHRoPTE0LCBmaWcuaGVpZ2h0PTd9Cm9yaWcucmFuay5kZiA8LSBvcmlnLmZyYWMuZGYgJT4lIAogIG11dGF0ZShvcmlnLnJhbms9ZGVuc2VfcmFuayhmcmFjLmxhYmVsKSkgJT4lCiAgc2VsZWN0KG9yaWcucmFuaywgcHJlZGljdGVkLmlkKSAlPiUKICBkaXN0aW5jdCgpICU+JQogIGFycmFuZ2Uob3JpZy5yYW5rKSAlPiUKICBjb2x1bW5fdG9fcm93bmFtZXMoInByZWRpY3RlZC5pZCIpIAoKcHJlZC5sYWJlbHMuZGYgJT4lCiAgZ3JvdXBfYnkobWV0aG9kKSAlPiUKICBkcm9wX25hKCkgJT4lCiAgbXV0YXRlKHRvdC5jZWxscz1uKCkpICU+JQogIHVuZ3JvdXAoKSAlPiUKICBncm91cF9ieShtZXRob2QsIHByZWRpY3RlZC5pZCkgJT4lCiAgc3VtbWFyaXNlKHRvdC5sYWJlbCA9IG4oKSwgdG90LmNlbGxzID0gbWF4KHRvdC5jZWxscyksIG1lYW4uc2NvcmU9bWVhbihzY29yZSkpICU+JQogIG11dGF0ZShmcmFjLmxhYmVsPXRvdC5sYWJlbC90b3QuY2VsbHMpICU+JQogIGJpbmRfcm93cyhvcmlnLmZyYWMuZGYpICU+JQogIG11dGF0ZShvcmlnLnJhbmsgPSBvcmlnLnJhbmsuZGZbcHJlZGljdGVkLmlkLF0pICU+JQogIG11dGF0ZShwcmVkaWN0ZWQuaWQ9ZmFjdG9yKHByZWRpY3RlZC5pZCwgbGV2ZWxzPXJvd25hbWVzKG9yaWcucmFuay5kZikpKSU+JQogICMgc2VsZWN0KG1ldGhvZCwgcHJlZGljdGVkLmlkLCBmcmFjLmxhYmVsKSAlPiUKICAjIGRpc3RpbmN0KCkgJT4lCiAgZ2dwbG90KGFlcyhwcmVkaWN0ZWQuaWQsIGZyYWMubGFiZWwsIGZpbGw9bWVhbi5zY29yZSwgY29sb3I9bWVhbi5zY29yZSkpICsKICBnZW9tX3BvaW50KHNpemU9MikgKwogIGdlb21fY29sKHdpZHRoPTAuMDUpICsKICBjb29yZF9mbGlwKCkgKwogICMgZ2VvbV9saW5lKGFlcyhncm91cD1tZXRob2QpKSArCiAgZmFjZXRfd3JhcChtZXRob2R+LiwgbnJvdz0xLCBuY29sPTQsIHNjYWxlcz0iZnJlZV94IikgKwogIHNjYWxlX2NvbG9yX3ZpcmlkaXNfYygpICsKICBzY2FsZV9maWxsX3ZpcmlkaXNfYygpICsKICB5bGFiKCJGcmFjdGlvbiBvZiBjZWxscyIpICsKICB0aGVtZV9idyhiYXNlX3NpemUgPSAxNikgKwogIGdnc2F2ZShwYXN0ZTAob3V0ZGlyLCAiY2VsbF90eXBlX2NvbXBvc2l0aW9uX2JhcnMucG5nIiksIHdpZHRoID0gMTUsIGhlaWdodCA9IDcpCmBgYAoKPCEtLSBEb2VzIHRoZSB1bmNlcnRhaW50eSBkZXBlbmQgb24gdGhlIHNpemUgb2YgdGhlIGNsdXN0ZXI/IC0tPgo8IS0tIGBgYHtyLCBmaWcud2lkdGg9MTQsIGZpZy5oZWlnaHQ9NX0gLS0+Cgo8IS0tIHByZWQubGFiZWxzLmRmICU+JSAtLT4KPCEtLSAgIGdyb3VwX2J5KG1ldGhvZCkgJT4lIC0tPgo8IS0tICAgZHJvcF9uYSgpICU+JSAtLT4KPCEtLSAgIG11dGF0ZSh0b3QuY2VsbHM9bigpKSAlPiUgLS0+CjwhLS0gICB1bmdyb3VwKCkgJT4lIC0tPgo8IS0tICAgZ3JvdXBfYnkobWV0aG9kLCBwcmVkaWN0ZWQuaWQpICU+JSAtLT4KPCEtLSAgIHN1bW1hcmlzZSh0b3QubGFiZWwgPSBuKCksIHRvdC5jZWxscyA9IG1heCh0b3QuY2VsbHMpLCBtZWFuLnNjb3JlPW1lZGlhbihzY29yZSksIHNkLnNjb3JlPW1hZChzY29yZSkpICU+JSAtLT4KPCEtLSAgIG11dGF0ZShmcmFjLmxhYmVsPXRvdC5sYWJlbC90b3QuY2VsbHMpICU+JSAtLT4KPCEtLSAgICMgYmluZF9yb3dzKG9yaWcuZnJhYy5kZikgJT4lIC0tPgo8IS0tICAgZ2dwbG90KGFlcyhmcmFjLmxhYmVsLCBtZWFuLnNjb3JlLCBjb2xvcj1tZXRob2QpKSArIC0tPgo8IS0tICAgZ2VvbV9wb2ludChzaXplPTIpICsgLS0+CjwhLS0gICBnZW9tX2Vycm9yYmFyKGFlcyh5bWluPW1lYW4uc2NvcmUtc2Quc2NvcmUsIHltYXg9bWVhbi5zY29yZStzZC5zY29yZSksIGFscGhhPTAuNikgKyAtLT4KPCEtLSAgIHNjYWxlX2NvbG9yX2JyZXdlcihwYWxldHRlPSJTZXQxIikgKyAtLT4KPCEtLSAgICMgZ2VvbV9zbW9vdGgobWV0aG9kID0gImxvZXNzIiwgc3Bhbj0xLjIpICsgLS0+CjwhLS0gICBmYWNldF9ncmlkKC4gfiBtZXRob2QpICsgLS0+CjwhLS0gICB0aGVtZV9idyhiYXNlX3NpemUgPSAxNikgKyAtLT4KPCEtLSAgIHN0YXRfY29yKGxhYmVsLnggPSAwLjIsIGxhYmVsLnk9MC4yNSwgY29sb3I9ImJsYWNrIiwgc2l6ZT01KSAgLS0+CgoKPCEtLSBgYGAgLS0+CgojIyMgQWdyZWVtZW50IHdpdGggdW5zdXBlcnZpc2VkIGNsdXN0ZXJpbmcgb2YgQVRBQyBkYXRhCkNhbGN1bGF0ZSB3aGljaCBmcmFjdGlvbnMgb2YgTk5zIGluIGJpbiBiYXNlZCBncmFwaCBvZiBBVEFDIGNlbGxzIGhhdmUgdGhlIHNhbWUgYW5ub3RhdGlvbgpgYGB7cn0KayA9IDMwCmF0YWMuc2V1IDwtIEZpbmROZWlnaGJvcnMoYXRhYy5zZXUsIGFzc2F5ID0gIkFUQUMiLCByZWR1Y3Rpb24gPSAiU25hcEFUQUMiLCBkaW1zID0gMToxNSwgay5wYXJhbSA9IGspCgphdGFjLm5uLmxpc3QgPC0gZ2V0Tk5saXN0KGF0YWMuc2V1KQoKc2NvcmUuQ0NBIDwtIGltYXBfZGJsKGF0YWMubm4ubGlzdCwgfiBzdW0ocHJlZC5jY2FbLngsMV0gPT0gcHJlZC5jY2FbLnksMV0pL2spICU+JSBzZXROYW1lcyhuYW1lcyhhdGFjLm5uLmxpc3QpKQpzY29yZS5Db25vcyA8LSBpbWFwX2RibChhdGFjLm5uLmxpc3QsIH4gc3VtKHByZWQuY29ub3NbLngsMV0gPT0gcHJlZC5jb25vc1sueSwxXSkvaykgJT4lIHNldE5hbWVzKG5hbWVzKGF0YWMubm4ubGlzdCkpCnNjb3JlLkxpZ2VyIDwtIGltYXBfZGJsKGF0YWMubm4ubGlzdCwgfiBzdW0ocHJlZC5saWdlclsueCwxXSA9PSBwcmVkLmxpZ2VyWy55LDFdKS9rKSAlPiUgc2V0TmFtZXMobmFtZXMoYXRhYy5ubi5saXN0KSkKCmtubl9zY29yZV9kZiA8LQogIGFzLmRhdGEuZnJhbWUoY2JpbmQoc2NvcmUuQ29ub3MsIHNjb3JlLkxpZ2VyLCBzY29yZS5DQ0EpKSAlPiUKICByb3duYW1lc190b19jb2x1bW4oImNlbGwiKSAlPiUKICBwaXZvdF9sb25nZXIoY29scz1zdHJfc3Vic2V0KGNvbG5hbWVzKC4pLCAic2NvcmUiKSwgbmFtZXNfdG8gPSAibWV0aG9kIiwgdmFsdWVzX3RvID0gIktOTl9zY29yZSIpICU+JQogIGRwbHlyOjptdXRhdGUoS05OX3Njb3JlPWlmZWxzZShpcy5uYShLTk5fc2NvcmUpLCAwLCBLTk5fc2NvcmUpLAogICAgICAgICAgICAgICAgbWV0aG9kPXN0cl9yZW1vdmUobWV0aG9kLCAic2NvcmUuIikpCgpxdWFudHMgPSBzZXEoMCwxLCBieSA9IDAuMDUpCkFVRUNERl9rbm5fc2NvcmUgPC0ga25uX3Njb3JlX2RmICU+JQogIHNwbGl0KC4kbWV0aG9kKSAlPiUKICBtYXBfZGJsKCB+IC54ICU+JQogICAgICBhcnJhbmdlKEtOTl9zY29yZSkgJT4lIAogICAgICB7ZWNkZiguJEtOTl9zY29yZSkocXVhbnRzKX0gJT4lIEFVQyhxdWFudHMsLikKICAgICkKICAKa25uX3Njb3JlX2RmICU+JQogIG11dGF0ZShBVUM9QVVFQ0RGX2tubl9zY29yZVttZXRob2RdKSAlPiUKICBnZ3Bsb3QoYWVzKEtOTl9zY29yZSwgY29sb3I9bWV0aG9kLCBmaWxsPW1ldGhvZCkpICsKICBzdGF0X2VjZGYoc2l6ZT0xKSArCiAgc2NhbGVfY29sb3JfYnJld2VyKHBhbGV0dGUgPSAiU2V0MSIpICsKICBnZW9tX3RleHQoZGF0YT0uICU+JSBncm91cF9ieShtZXRob2QpICU+JSBzdW1tYXJpc2UoQVVDPW1heChBVUMpKSwgCiAgICAgICAgICAgIHg9MC4wNSwgaGp1c3Q9MCwKICAgICAgICAgICAgYWVzKGxhYmVsPWdsdWUoIkFVQyA9IHtyb3VuZChBVUMsIDMpfSIpLCB5PWMoMC45MCwgMC45NSwgMSkpKSArCiAgdGhlbWVfYncoYmFzZV9zaXplID0gMTYpICsKICB5bGFiKCJFQ0RGIikgKwogIGdnc2F2ZShwYXN0ZShvdXRkaXIsIktOTl9zY29yZV9lY2RmX3VuaW9uSFZHLnBuZyIpLCBoZWlnaHQgPSA0LCB3aWR0aD02KQpgYGAKCmBgYHtyLCBmaWcuaGVpZ2h0PTgsIGZpZy53aWR0aD04fQoKZnVsbF9qb2luKHByZWQubGFiZWxzLmRmLCBrbm5fc2NvcmVfZGYpICU+JQogIGdncGxvdChhZXMoS05OX3Njb3JlLCBjb2xvcj1tZXRob2QpKSArCiAgc3RhdF9lY2RmKCkgKwogIGZhY2V0X3dyYXAoInByZWRpY3RlZC5pZCIpICsKICBzY2FsZV9jb2xvcl9icmV3ZXIocGFsZXR0ZSA9ICJTZXQxIikgKwogIGNvb3JkX2ZpeGVkKCkKYGBgCgo8IS0tICMjIyMgV2hpY2ggY2VsbHMgYXJlIGluY29uc2lzdGVudGx5IGFsaWduZWQ/IC0tPgo8IS0tIGBgYHtyLCBmaWcud2lkdGg9MTQsIGZpZy5oZWlnaHQ9MTB9IC0tPgo8IS0tIHByZWQubGFiZWxzLmRmICU+JSAtLT4KPCEtLSAgIHNlbGVjdChtZXRob2QsIHByZWRpY3RlZC5pZCwgY2VsbCkgJT4lIC0tPgo8IS0tICAgbXV0YXRlKHByZWRpY3RlZC5pZD1pZmVsc2UoaXMubmEocHJlZGljdGVkLmlkKSwgIm5vbmUiLCBwcmVkaWN0ZWQuaWQpKSAlPiUgLS0+CjwhLS0gICBnZ3Bsb3QoYWVzKHg9bWV0aG9kLCBzdHJhdHVtPXByZWRpY3RlZC5pZCwgYWxsdXZpdW09Y2VsbCwgZmlsbD1wcmVkaWN0ZWQuaWQsIGxhYmVsPXByZWRpY3RlZC5pZCkpICsgLS0+CjwhLS0gICBnZW9tX2Zsb3coKSArIC0tPgo8IS0tICAgZ2VvbV9zdHJhdHVtKGNvbG9yPU5BKSArIC0tPgo8IS0tICAgZ2VvbV90ZXh0KHN0YXQ9InN0cmF0dW0iKSArIC0tPgo8IS0tICAgdGhlbWVfYncoYmFzZV9zaXplID0gMTYpIC0tPgo8IS0tIGBgYCAtLT4KPCEtLSAjIyMjIFdoaWNoIGNlbGxzIGFyZSBpbmNvbnNpc3RlbnRseSBzY29yZWQ/IC0tPgo8IS0tIGBgYHtyLCBmaWcud2lkdGg9MTQsIGZpZy5oZWlnaHQ9OH0gLS0+CjwhLS0gbGlicmFyeShnZ2FsbHV2aWFsKSAtLT4KPCEtLSBwcmVkLmxhYmVscy5kZiAlPiUgLS0+CjwhLS0gICBzZWxlY3QobWV0aG9kLCBwcmVkaWN0ZWQuaWQsIGNlbGwpICU+JSAtLT4KPCEtLSAgIG11dGF0ZShwcmVkaWN0ZWQuaWQ9aWZlbHNlKGlzLm5hKHByZWRpY3RlZC5pZCksICJub25lIiwgcHJlZGljdGVkLmlkKSkgJT4lIC0tPgo8IS0tICAgZ2dwbG90KGFlcyh4PW1ldGhvZCwgc3RyYXR1bT1wcmVkaWN0ZWQuaWQsIGFsbHV2aXVtPWNlbGwsIGZpbGw9cHJlZGljdGVkLmlkLCBsYWJlbD1wcmVkaWN0ZWQuaWQpKSArIC0tPgo8IS0tICAgZ2VvbV9mbG93KCkgKyAtLT4KPCEtLSAgIGdlb21fc3RyYXR1bSgpICsgLS0+CjwhLS0gICBnZW9tX3RleHQoc3RhdD0ic3RyYXR1bSIpICsgLS0+CjwhLS0gICB0aGVtZV9idyhiYXNlX3NpemUgPSAxNikgLS0+CjwhLS0gYGBgIC0tPgoKIyMgQWNjZXNzaWJpbGl0eSBvZiBtYXJrZXJzClRha2luZyBtYXJrZXJzIGZyb20gRmlnLiBTMiBvZiBKUCdzIG1hbnVzY3JpcHQKYGBge3IsIGZpZy5oZWlnaHQ9MTMsIGZpZy53aWR0aD0xMCwgd2FybmluZz1GQUxTRSwgbWVzc2FnZT1GQUxTRX0KdGh5bXVzLm1hcmtlcnMgPC0gYygiUFRQUkMiLCAiQ0QzRyIsICJUWVJPQlAiLCJDRDE5IiwiSE9YQTkiLCdGWFlEMicsIlNIM1RDMSIsIkNDUjkiLCJDRDhBIiwgIkNEOEIiLCJQRENEMSIsICJDUlRBTSIsIkNENDBMRyIsIkNDUjYiLCJGT1hQMyIsIlNPWDEzIiwiWk5GNjgzIiwiS0xSRDEiLCJUTkZTRjExIiwiVlBSRUIxIiwiTVM0QTEiLCAiQ0xFQzlBIiwgIkNMRUMxMEEiLCAiTEFNUDMiLCAiSUwzUkEiLCAiRkNHUjNCIiwgIkMyIiwiVFBTQjIiLAogICAgICAgICAgICAgICAgICAgICdJVEdBMkInLCJHWVBBIiwgIkNESDUiLCAiUkdTNSIsIkNESDEiLCAiUERHRlJBIiwiQ1JBQlAxIikKIyBwYm1jLm1hcmtlcnMgPC0gYygiQ0Q3OUEiLCAiTVM0QTEiLCAiQ0Q4QSIsICJDRDhCIiwgIkxZWiIpCiMgdGh5bXVzLm1hcmtlcnMgPC0gbGlzdChGYj1jKCJQREdGUkEiLCAiQ09MRUMxMSIsICJGQk4xIiwgIlBJMTYiKSwKIyAgICAgICAgICAgICAgICAgICAgICAgIFZTTUM9YygiUERHRlJCIiwgJ0FDVEEyJywgIlJHUzUiKSwKIyAgICAgICAgICAgICAgICAgICAgICAgIEVuZG89YygiUEVDQU0xIiwgIkNESDUiLCJMWVZFMSIpLAojICAgICAgICAgICAgICAgICAgICAgICAgVEVDID0gYygiRVBDQU0iLCAiRk9YTjEiLCAiQ0NMMjUiLCAiQ0NMMTkiKQojICAgICAgICAgICAgICAgICAgICAgICAgKQp0aHltdXMubWFya2Vycy5kZiA8LSBpbWFwKHRoeW11cy5tYXJrZXJzLCB+IGRhdGEuZnJhbWUoZ2VuZT0ueCwgY2VsbC50eXBlLmNsYXNzPS55KSkgJT4lCiAgcHVycnI6OnJlZHVjZShiaW5kX3Jvd3MpCgptYXJrZXIuYWNjZXNzLmRmIDwtIGF0YWMuc2V1QGFzc2F5cyRBQ1RJVklUWUBkYXRhW2ludGVyc2VjdCh0aHltdXMubWFya2Vycywgcm93bmFtZXMoYXRhYy5zZXVAYXNzYXlzJEFDVElWSVRZKSksXSAlPiUKICBhcy5tYXRyaXgoKSAlPiUKICByZXNoYXBlMjo6bWVsdCh2YXJuYW1lcz1jKCJnZW5lIiwgImNlbGwiKSwgdmFsdWUubmFtZT0ibG9nLmNvdW50cyIpICU+JQogIGZ1bGxfam9pbihyb3duYW1lc190b19jb2x1bW4oYXRhYy5zZXVAbWV0YS5kYXRhWywgbGFiZWxfY29sc10sICJjZWxsIikpICU+JQogICMgZnVsbF9qb2luKHRoeW11cy5tYXJrZXJzLmRmKSAlPiUKICBwaXZvdF9sb25nZXIoY29scz1sYWJlbF9jb2xzLCBuYW1lc190byA9ICJtZXRob2QiLCB2YWx1ZXNfdG8gPSAicHJlZGljdGVkLmlkIikgJT4lCiAgZHBseXI6Om11dGF0ZShtZXRob2Q9c3RyX3JlbW92ZShtZXRob2QsIi4rXyIpKSAlPiUKICBmaWx0ZXIobWV0aG9kICVpbiUgYygiQ0NBIiwgIkxpZ2VyIiwgIkNvbm9zIikpIAoKb3JkZXJlZF9jZWxsX3R5cGVzIDwtIGMoIkROIiwgIkRQIChRKSIsICJEUCAoUCkiLCAiU1AiLCAiTksiLCAiSUxDMyIsICJEQyIsICJNYWMiLCAiRXJ5IiwgIkZpYiIpCgptYXJrZXJzX3BsIDwtIAogIG1hcmtlci5hY2Nlc3MuZGYgJT4lCiAgbXV0YXRlKHByZWRpY3RlZC5pZCA9IGNhc2Vfd2hlbihzdHJfZGV0ZWN0KHByZWRpY3RlZC5pZCwgIkNEOCIpIH4gIkNEOCtUIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgc3RyX2RldGVjdChwcmVkaWN0ZWQuaWQsICJDRDQiKSB+ICJDRDQrVCIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBUUlVFIH4gcHJlZGljdGVkLmlkCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICApCiAgICAgICAgICkgJT4lCiAgbXV0YXRlKHByZWRpY3RlZC5pZD1mYWN0b3IocHJlZGljdGVkLmlkLCBsZXZlbHMgPSBvcmRlcmVkX2NlbGxfdHlwZXMpKSAlPiUKICBncm91cF9ieShtZXRob2QsIHByZWRpY3RlZC5pZCwgZ2VuZSkgJT4lCiAgZHBseXI6Om11dGF0ZShmcmFjLmNlbGxzPXN1bShsb2cuY291bnRzID4gMCkvbigpKSAlPiUKICAjIGZpbHRlcihtZXRob2Q9PSJDQ0EiKSAlPiUKICB1bmdyb3VwKCkgJT4lCiAgZ2dwbG90KCBhZXMoIGdlbmUsIHByZWRpY3RlZC5pZCkpICsKICBnZW9tX3BvaW50KGFlcyhzaXplPWZyYWMuY2VsbHMsIGNvbG9yPWZyYWMuY2VsbHMpKSArCiAgZmFjZXRfZ3JpZChtZXRob2R+Liwgc3BhY2U9ImZyZWUiLCBzY2FsZXM9ImZyZWVfeCIpICsKICBzY2FsZV9jb2xvcl9ncmFkaWVudChoaWdoPSJkYXJrYmx1ZSIsIGxvdz0id2hpdGUiKSArCiAgIyBzY2FsZV9jb2xvcl92aXJpZGlzX2MoKSArCiAgdGhlbWVfYncoYmFzZV9zaXplID0gMTYpICsKICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZT05MCwgaGp1c3Q9MSwgdmp1c3Q9MC41KSwKICAgICAgICBzdHJpcC50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGU9NDUpKSAKCm1hcmtlcnNfcGwgCiAgCmdnc2F2ZShwYXN0ZTAob3V0ZGlyLCAiVGh5bXVzX21hcmtlcnNfYWNjZXNzaWJpbGl0eS5wbmciKSwgaGVpZ2h0ID0gMTYsIHdpZHRoID0gMTIpCmBgYAoKUmVwcm9kdWNpbmcgRmlnLjJIIG9uIFQtY2VsbCBkZXZlbG9wbWVudApgYGB7ciwgZmlnLndpZHRoPTEwLCBmaWcuaGVpZ2h0PTEyfQp0LmNlbGwubWFya2VycyA8LSBsaXN0KGtub3duLm1hcmtlcnMgPSBjKCJDRDM0IiwgIklHTEwxIiwgIlRSR0MyIiwgIlRSREMiLCAiUFRDUkEiLCAiVFJCQzIiLCAiVFJBQyIsICJDRDQiLCAiQ0Q4QSIsICJDRDhCIiksCiAgICAgICAgICAgICAgICAgICAgICAgY2hlbW9raW5lLnJlY2VwdG9ycyA9IGMoIkNDUjkiLCAiQ0NSNyIpLAogICAgICAgICAgICAgICAgICAgICAgIHRjci5hY3RpdmF0aW9uID0gYygiQ0Q1IiwgIkNEMjciKSwKICAgICAgICAgICAgICAgICAgICAgICBwcm9saWZlcmF0aW9uPWMoIlBDTkEiLCAiQ0RLMSIsICJNS0k2NyIpLAogICAgICAgICAgICAgICAgICAgICAgIGN5Y2xpbi5EID0gYygiQ0NORDIiLCAiQ0NORDMiKSwKICAgICAgICAgICAgICAgICAgICAgICByZWNvbWJpbmF0aW9uPWMoIlJBRzEiLCAiUkFHMiIpLAogICAgICAgICAgICAgICAgICAgICAgIGFwb3B0b3Npcz1jKCJIUksiLCJCTUYiLCAiVFA1M0lOUDEiKSwKICAgICAgICAgICAgICAgICAgICAgICBzdGFnZS5tYXJrZXJzID0gYygiU1QxOCIsICJISVZFUDMiLCAiUkdQRDMiLCAiU01QRDMiLCAiQVFQMyIsICJST1JDIiwgIlNBVEIxIiwgIlRPWDIiKQogICAgICAgICAgICAgICAgICAgICAgICkgCnQuY2VsbC5tYXJrZXJzLmRmIDwtIGltYXAodC5jZWxsLm1hcmtlcnMsIH4gZGF0YS5mcmFtZShnZW5lPS54LCBjZWxsLnR5cGUuY2xhc3M9LnkpKSAlPiUKICBwdXJycjo6cmVkdWNlKGJpbmRfcm93cykKCm9yZGVyZWQudGNlbGxzIDwtIGMoIkROIiwgIkRQIChQKSIsICJEUCAoUSkiLCJTUCIpCgp0Y2VsbHMubWFya2Vycy5kZiA8LSAKICBhdGFjLnNldUBhc3NheXMkQUNUSVZJVFlAZGF0YVtpbnRlcnNlY3QodW5saXN0KHQuY2VsbC5tYXJrZXJzKSwgcm93bmFtZXMoYXRhYy5zZXVAYXNzYXlzJEFDVElWSVRZKSksXSAlPiUKICBhcy5tYXRyaXgoKSAlPiUKICByZXNoYXBlMjo6bWVsdCh2YXJuYW1lcz1jKCJnZW5lIiwgImNlbGwiKSwgdmFsdWUubmFtZT0ibG9nLmNvdW50cyIpICU+JQogIGZ1bGxfam9pbihyb3duYW1lc190b19jb2x1bW4oYXRhYy5zZXVAbWV0YS5kYXRhWywgbGFiZWxfY29sc10sICJjZWxsIikpICU+JQogIHBpdm90X2xvbmdlcihjb2xzPWxhYmVsX2NvbHMsIG5hbWVzX3RvID0gIm1ldGhvZCIsIHZhbHVlc190byA9ICJwcmVkaWN0ZWQuaWQiKSAlPiUKICBkcGx5cjo6bXV0YXRlKG1ldGhvZD1zdHJfcmVtb3ZlKG1ldGhvZCwiLitfIikpICU+JQogIGZpbHRlcihtZXRob2QgJWluJSBjKCJDQ0EiLCAiTGlnZXIiLCAiQ29ub3MiKSkgJT4lCiAgbXV0YXRlKHByZWRpY3RlZC5pZD1pZmVsc2Uoc3RyX2RldGVjdChwcmVkaWN0ZWQuaWQsICJDRDgrIiksICJDRDgrVCIsIHByZWRpY3RlZC5pZCkpICU+JQogIG11dGF0ZShwcmVkaWN0ZWQuaWQ9aWZlbHNlKHN0cl9kZXRlY3QocHJlZGljdGVkLmlkLCAiQ0Q0KyIpLCAiQ0Q0K1QiLCBwcmVkaWN0ZWQuaWQpKSAlPiUKICBmaWx0ZXIocHJlZGljdGVkLmlkICVpbiUgb3JkZXJlZC50Y2VsbHMpICU+JQogIGdyb3VwX2J5KG1ldGhvZCwgcHJlZGljdGVkLmlkLCBnZW5lKSAlPiUKICBkcGx5cjo6bXV0YXRlKGZyYWMuY2VsbHM9c3VtKGxvZy5jb3VudHMgPiAwKS9uKCksIG1lYW4uYWNjPW1lYW4obG9nLmNvdW50cykpICU+JQogIHVuZ3JvdXAoKSAKCnRjZWxscy5tYXJrZXJzLmRmICU+JQogIGZ1bGxfam9pbih0LmNlbGwubWFya2Vycy5kZikgJT4lCiAgIyBmaWx0ZXIobWV0aG9kPT0iQ0NBIikgJT4lCiAgbXV0YXRlKHByZWRpY3RlZC5pZD1mYWN0b3IocHJlZGljdGVkLmlkLCBsZXZlbHM9b3JkZXJlZC50Y2VsbHMpKSAlPiUKICBnZ3Bsb3QoYWVzKCBwcmVkaWN0ZWQuaWQsIGdlbmUpKSArCiAgZmFjZXRfZ3JpZChjZWxsLnR5cGUuY2xhc3N+bWV0aG9kLCBzY2FsZXMgPSAiZnJlZV95Iiwgc3BhY2U9ImZyZWUiKSArCiAgZ2VvbV9wb2ludChhZXMoc2l6ZT1mcmFjLmNlbGxzLCBjb2xvcj1tZWFuLmFjYykpICsKICBzY2FsZV9jb2xvcl9ncmFkaWVudChoaWdoPSJkYXJrYmx1ZSIsIGxvdz0id2hpdGUiKSArCiAgIyBzY2FsZV9jb2xvcl9ncmFkaWVudDIobWlkcG9pbnQgPSAwLjUpICsKICB0aGVtZV9idyhiYXNlX3NpemUgPSAxNikgKwogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlPTkwLCBoanVzdD0xLCB2anVzdD0wLjUpLAogICAgICAgIHN0cmlwLnRleHQueSA9IGVsZW1lbnRfdGV4dChhbmdsZT0wKSkgCgpnZ3NhdmUocGFzdGUwKG91dGRpciwgInRjZWxsX21hcmtlcnMucG5nIiksIGhlaWdodCA9IDE0LCB3aWR0aCA9IDE0KQoKYGBgCgo8IS0tICMjIyBDb21wYXJlIGZlYXR1cmUgc2VsZWN0aW9uIHN0cmF0ZWd5IChyZWZlcmVuY2UgYmFzZWQpIC0tPgo8IS0tIGBgYHtyfSAtLT4KPCEtLSBzZXUuY2NhLnJlZiA8LSByZWFkUkRTKCJ+L21vZGVscy9sYWJlbFRyYW5zZmVyQ0NBX3JlZmVyZW5jZV9odmdfRjc0X1NDRWxpc3RfMjAxOTExMDEuUkRTIikgLS0+CjwhLS0gc2V1LmxpZ2VyLnJlZiA8LSByZWFkUkRTKCJ+L21vZGVscy9sYWJlbFRyYW5zZmVyTGlnZXJfcmVmZXJlbmNlX2h2Z19GNzRfU0NFbGlzdF8yMDE5MTEwMS5SRFMiKSAtLT4KPCEtLSBzZXUuY29ub3MucmVmIDwtIHJlYWRSRFMoIn4vbW9kZWxzL2xhYmVsVHJhbnNmZXJDb25vc19yZWZlcmVuY2VfaHZnX0Y3NF9TQ0VsaXN0XzIwMTkxMTAxLlJEUyIpIC0tPgoKPCEtLSBpbnRlZ3JhdGVfZmVhdHVyZXNfcmVmIDwtIHNjYW4oIn4vbW9kZWxzL2ludEZlYXR1cmVzX3JlZmVyZW5jZV9odmdfMjAwMF9GNzRfU0NFbGlzdF8yMDE5MTEwMS50eHQiLCB3aGF0ID0gIiIpIC0tPgoKPCEtLSBpbnQubGlzdC5yZWYgPC0gbGlzdChDQ0E9c2V1LmNjYS5yZWYsIExpZ2VyPXNldS5saWdlci5yZWYsIENvbm9zPXNldS5jb25vcy5yZWYpIC0tPgoKPCEtLSAjIyBBZGQgdG8gYXRhYyBTZXVyYXQgb2JqZWN0IC0tPgo8IS0tIHByZWQuY2NhLnJlZiA8LSBnZXRQcmVkaWN0ZWRMYWJlbHMoc2V1LmNjYS5yZWYsICJDQ0FfcmVmIiwgc2NvcmUuY29sID0gInByZWRpY3Rpb24uc2NvcmUubWF4IikgLS0+CjwhLS0gcHJlZC5saWdlci5yZWYgPC0gZ2V0UHJlZGljdGVkTGFiZWxzKHNldS5saWdlci5yZWYsICJMaWdlcl9yZWYiKSAtLT4KPCEtLSBwcmVkLmNvbm9zLnJlZiA8LSBnZXRQcmVkaWN0ZWRMYWJlbHMoc2V1LmNvbm9zLnJlZiwgIkNvbm9zX3JlZiIpIC0tPgoKPCEtLSBpZiAoYWxsKHJvd25hbWVzKHByZWQuY29ub3MpID09IHJvd25hbWVzKHByZWQuY2NhKSkgJiBhbGwocm93bmFtZXMocHJlZC5jb25vcykgPT0gcm93bmFtZXMocHJlZC5saWdlcikpKSB7IC0tPgo8IS0tICAgYXRhYy5zZXUgPC0gQWRkTWV0YURhdGEoYXRhYy5zZXUsIG1ldGFkYXRhID0gY2JpbmQocHJlZC5jY2EucmVmLCBwcmVkLmxpZ2VyLnJlZiwgcHJlZC5jb25vcy5yZWYpKSAtLT4KPCEtLSB9IGVsc2UgeyAtLT4KPCEtLSAgIHN0b3AoIk5vbiBjb3JyZXNwb25kaW5nIGNlbGwgbmFtZXMiKSAtLT4KPCEtLSB9IC0tPgoKPCEtLSBgYGAgLS0+Cgo8IS0tIGBgYHtyLCBmaWcud2lkdGg9MTksIGZpZy5oZWlnaHQ9OX0gLS0+CjwhLS0gZ2dwdWJyOjpnZ2FycmFuZ2UoIC0tPgo8IS0tICAgcGxvdGxpc3QgPSBsaXN0KCAtLT4KPCEtLSAgICAgRGltUGxvdChhdGFjLnNldSwgcmVkdWN0aW9uID0gInVtYXAuc25hcCIsIGdyb3VwLmJ5ID0gInByZWRpY3RlZC5pZF9DQ0FfcmVmIiAgLCBjb2xzPWNlbGwudHlwZS5wYWwsIGxhYmVsPVRSVUUsIHJlcGVsPVRSVUUpICsgZ2d0aXRsZSgiQ0NBIiksIC0tPgo8IS0tICAgICBEaW1QbG90KGF0YWMuc2V1LCByZWR1Y3Rpb24gPSAidW1hcC5zbmFwIiwgZ3JvdXAuYnkgPSAicHJlZGljdGVkLmlkX0xpZ2VyX3JlZiIsIGNvbHM9Y2VsbC50eXBlLnBhbCwgbGFiZWw9VFJVRSwgcmVwZWw9VFJVRSkgKyBnZ3RpdGxlKCJMaWdlciIpLCAtLT4KPCEtLSAgICAgRGltUGxvdChhdGFjLnNldSwgcmVkdWN0aW9uID0gInVtYXAuc25hcCIsIGdyb3VwLmJ5ID0gInByZWRpY3RlZC5pZF9Db25vc19yZWYiLCBjb2xzPWNlbGwudHlwZS5wYWwsIGxhYmVsPVRSVUUsIHJlcGVsPVRSVUUpICsgZ2d0aXRsZSgiQ29ub3MiKSAtLT4KPCEtLSAgICksIC0tPgo8IS0tICAgY29tbW9uLmxlZ2VuZCA9IFRSVUUsIG5jb2w9MywgbnJvdz0xIC0tPgo8IS0tICkgIC0tPgo8IS0tIGBgYCAtLT4KCjwhLS0gYGBge3IsIGZpZy5oZWlnaHQ9MTAsIGZpZy53aWR0aD0xNn0gLS0+CjwhLS0gcHJlZC5sYWJlbHMucmVmLmRmIDwtIGltYXAobGlzdChDQ0E9cHJlZC5jY2EucmVmLCBMaWdlcj1wcmVkLmxpZ2VyLnJlZiwgQ29ub3M9cHJlZC5jb25vcy5yZWYpLCB+ICAtLT4KPCEtLSAgICAgICByb3duYW1lc190b19jb2x1bW4oLngsICJjZWxsIikgJT4lIC0tPgo8IS0tICAgICAgIHJlbmFtZV9hbGwoZnVucyhzdHJfcmVtb3ZlKC4sIHN0cl9jKCJfIiwueSkpKSkgJT4lIC0tPgo8IS0tICAgICAgIG11dGF0ZShtZXRob2Q9LnkpIC0tPgo8IS0tICAgICApICU+JSAtLT4KPCEtLSAgIHB1cnJyOjpyZWR1Y2UoYmluZF9yb3dzKSAlPiUgLS0+CjwhLS0gICBtdXRhdGUoc2NvcmU9aWZlbHNlKGlzLm5hKHNjb3JlX3JlZiksIDAsIHNjb3JlX3JlZikpIC0tPgoKPCEtLSBmdWxsX2pvaW4oIC0tPgo8IS0tICAgcHJlZC5sYWJlbHMuZGYsIC0tPgo8IS0tICAgc2VsZWN0KHByZWQubGFiZWxzLnJlZi5kZiwgY2VsbCwgcHJlZGljdGVkLmlkX3JlZiwgc2NvcmVfcmVmLCBtZXRob2QpLCAtLT4KPCEtLSAgIGJ5PWMoImNlbGwiLCAibWV0aG9kIikgLS0+CjwhLS0gICApICU+JSAtLT4KPCEtLSAgIGdyb3VwX2J5KG1ldGhvZCwgcHJlZGljdGVkLmlkKSAlPiUgLS0+CjwhLS0gICBtdXRhdGUobl9wcmVkPW4oKSkgJT4lIC0tPgo8IS0tICAgdW5ncm91cCgpICU+JSAtLT4KPCEtLSAgIGdyb3VwX2J5KG1ldGhvZCwgcHJlZGljdGVkLmlkLCBwcmVkaWN0ZWQuaWRfcmVmKSAlPiUgLS0+CjwhLS0gICBzdW1tYXJpc2Uobj1uKCksIG5fcHJlZD1tYXgobl9wcmVkKSkgJT4lIC0tPgo8IS0tICAgbXV0YXRlKGZyYWM9bi9uX3ByZWQpICU+JSAtLT4KPCEtLSAgIGdncGxvdChhZXMocHJlZGljdGVkLmlkLCBwcmVkaWN0ZWQuaWRfcmVmKSkgKyAtLT4KPCEtLSAgIGdlb21fdGlsZShhZXMoZmlsbD1mcmFjKSkgKyAtLT4KPCEtLSAgIGZhY2V0X3dyYXAobWV0aG9kfi4sIG5yb3c9MSwgbmNvbD0zKSArIC0tPgo8IS0tICAgY29vcmRfZml4ZWQoKSArIC0tPgo8IS0tICAgc2NhbGVfZmlsbF9ncmFkaWVudChsb3c9IndoaXRlIiwgaGlnaD0icmVkIikgKyAtLT4KPCEtLSAgIHlsYWIoIkZlYXQuIHNlbGVjdGlvbjogcmVmZXJlbmNlIEhWRyIpICsgeGxhYigiRmVhdC4gc2VsZWN0aW9uOiB1bmlvbiBIVkciKSArIC0tPgo8IS0tICAgdGhlbWVfY293cGxvdChmb250X3NpemUgPSAxNikgKyAtLT4KPCEtLSAgIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlPTQ1LCBoanVzdD0xKSkgKyAtLT4KPCEtLSAgIGdnc2F2ZShwYXN0ZTAob3V0ZGlyLCAidW5pb25WU3JlZmVyZW5jZS5wbmciKSwgaGVpZ2h0ID0gMTIsIHdpZHRoPTEwKSAtLT4KPCEtLSBgYGAgLS0+CjwhLS0gYGBge3J9IC0tPgoKPCEtLSBzY29yZS5DQ0EucmVmIDwtICAgaW1hcF9kYmwoYXRhYy5ubi5saXN0LCB+IHN1bShwcmVkLmNjYS5yZWZbLngsMV0gPT0gcHJlZC5jY2EucmVmWy55LDFdKS9rKSAlPiUgc2V0TmFtZXMobmFtZXMoYXRhYy5ubi5saXN0KSkgLS0+CjwhLS0gc2NvcmUuQ29ub3MucmVmIDwtIGltYXBfZGJsKGF0YWMubm4ubGlzdCwgfiBzdW0ocHJlZC5jb25vcy5yZWZbLngsMV0gPT0gcHJlZC5jb25vcy5yZWZbLnksMV0pL2spICU+JSBzZXROYW1lcyhuYW1lcyhhdGFjLm5uLmxpc3QpKSAtLT4KPCEtLSBzY29yZS5MaWdlci5yZWYgPC0gaW1hcF9kYmwoYXRhYy5ubi5saXN0LCB+IHN1bShwcmVkLmxpZ2VyLnJlZlsueCwxXSA9PSBwcmVkLmxpZ2VyLnJlZlsueSwxXSkvaykgJT4lIHNldE5hbWVzKG5hbWVzKGF0YWMubm4ubGlzdCkpIC0tPgoKPCEtLSBrbm5fc2NvcmVfcmVmX2RmIDwtIC0tPgo8IS0tICAgYXMuZGF0YS5mcmFtZShjYmluZChzY29yZS5Db25vcy5yZWYsIHNjb3JlLkxpZ2VyLnJlZiwgc2NvcmUuQ0NBLnJlZikpICU+JSAtLT4KPCEtLSAgIHJvd25hbWVzX3RvX2NvbHVtbigiY2VsbCIpICU+JSAtLT4KPCEtLSAgIHBpdm90X2xvbmdlcihjb2xzPXN0cl9zdWJzZXQoY29sbmFtZXMoLiksICJzY29yZSIpLCBuYW1lc190byA9ICJtZXRob2QiLCB2YWx1ZXNfdG8gPSAiS05OX3Njb3JlIikgJT4lIC0tPgo8IS0tICAgZHBseXI6Om11dGF0ZShLTk5fc2NvcmU9aWZlbHNlKGlzLm5hKEtOTl9zY29yZSksIDAsIEtOTl9zY29yZSksIC0tPgo8IS0tICAgICAgICAgICAgICAgICBtZXRob2Q9c3RyX3JlbW92ZShtZXRob2QsICJzY29yZS4iKSkgLS0+Cgo8IS0tIHF1YW50cyA9IHNlcSgwLDEsIGJ5ID0gMC4wNSkgLS0+CjwhLS0gQVVFQ0RGX2tubl9zY29yZSA8LSBrbm5fc2NvcmVfcmVmX2RmICU+JSAtLT4KPCEtLSAgIHNwbGl0KC4kbWV0aG9kKSAlPiUgLS0+CjwhLS0gICBtYXBfZGJsKCB+IC54ICU+JSAtLT4KPCEtLSAgICAgICBhcnJhbmdlKEtOTl9zY29yZSkgJT4lICAtLT4KPCEtLSAgICAgICB7ZWNkZiguJEtOTl9zY29yZSkocXVhbnRzKX0gJT4lIEFVQyhxdWFudHMsLikgLS0+CjwhLS0gICAgICkgLS0+Cgo8IS0tIGtubl9zY29yZV9yZWZfZGYgJT4lIC0tPgo8IS0tICAgbXV0YXRlKEFVQz1BVUVDREZfa25uX3Njb3JlW21ldGhvZF0pICU+JSAtLT4KPCEtLSAgIGdncGxvdChhZXMoS05OX3Njb3JlLCBjb2xvcj1tZXRob2QsIGZpbGw9bWV0aG9kKSkgKyAtLT4KPCEtLSAgIHN0YXRfZWNkZihzaXplPTEpICsgLS0+CjwhLS0gICBzY2FsZV9jb2xvcl9icmV3ZXIocGFsZXR0ZSA9ICJTZXQxIikgKyAtLT4KPCEtLSAgIGdlb21fdGV4dChkYXRhPS4gJT4lIGdyb3VwX2J5KG1ldGhvZCkgJT4lIHN1bW1hcmlzZShBVUM9bWF4KEFVQykpLCAgLS0+CjwhLS0gICAgICAgICAgICAgeD0wLjA1LCBoanVzdD0wLCAtLT4KPCEtLSAgICAgICAgICAgICBhZXMobGFiZWw9Z2x1ZSgiQVVDID0ge3JvdW5kKEFVQywgMyl9IiksIHk9YygwLjkwLCAwLjk1LCAxKSkpICsgLS0+CjwhLS0gICB0aGVtZV9idyhiYXNlX3NpemUgPSAxNikgKyAtLT4KPCEtLSAgIHlsYWIoIkVDREYiKSAgLS0+CjwhLS0gYGBgIC0tPgoKPCEtLSAjIyMgSXMgdGhlIHVuaW9uIG9yIHRoZSByZWZlcmVuY2UgYmVzdCBtYWludGFpbmluZyB0aGUgc3RydWN0dXJlIG9mIHRoZSBBVEFDPyAtLT4KPCEtLSBgYGB7ciwgZmlnLndpZHRoPTE1LGZpZy5oZWlnaHQ9N30gLS0+CjwhLS0gayA9IDUwIC0tPgo8IS0tIGF0YWMuc2V1IDwtIEZpbmROZWlnaGJvcnMoYXRhYy5zZXUsIGFzc2F5ID0gIkFUQUMiLCByZWR1Y3Rpb24gPSAiU25hcEFUQUMiLCBkaW1zID0gMToxNSwgay5wYXJhbSA9IGspIC0tPgoKPCEtLSBhdGFjLm5uLmxpc3QgPC0gZ2V0Tk5saXN0KGF0YWMuc2V1KSAtLT4KCjwhLS0gc2NvcmUuQ0NBIDwtIGltYXBfZGJsKGF0YWMubm4ubGlzdCwgfiBzdW0ocHJlZC5jY2FbLngsMV0gPT0gcHJlZC5jY2FbLnksMV0pL2spICU+JSBzZXROYW1lcyhuYW1lcyhhdGFjLm5uLmxpc3QpKSAtLT4KPCEtLSBzY29yZS5Db25vcyA8LSBpbWFwX2RibChhdGFjLm5uLmxpc3QsIH4gc3VtKHByZWQuY29ub3NbLngsMV0gPT0gcHJlZC5jb25vc1sueSwxXSkvaykgJT4lIHNldE5hbWVzKG5hbWVzKGF0YWMubm4ubGlzdCkpIC0tPgo8IS0tIHNjb3JlLkxpZ2VyIDwtIGltYXBfZGJsKGF0YWMubm4ubGlzdCwgfiBzdW0ocHJlZC5saWdlclsueCwxXSA9PSBwcmVkLmxpZ2VyWy55LDFdKS9rKSAlPiUgc2V0TmFtZXMobmFtZXMoYXRhYy5ubi5saXN0KSkgLS0+Cgo8IS0tIGtubl9zY29yZV9kZiA8LSAtLT4KPCEtLSAgIGFzLmRhdGEuZnJhbWUoY2JpbmQoc2NvcmUuQ29ub3MsIHNjb3JlLkxpZ2VyLCBzY29yZS5DQ0EpKSAlPiUgLS0+CjwhLS0gICByb3duYW1lc190b19jb2x1bW4oImNlbGwiKSAlPiUgLS0+CjwhLS0gICBwaXZvdF9sb25nZXIoY29scz1zdHJfc3Vic2V0KGNvbG5hbWVzKC4pLCAic2NvcmUiKSwgbmFtZXNfdG8gPSAibWV0aG9kIiwgdmFsdWVzX3RvID0gIktOTl9zY29yZSIpICU+JSAtLT4KPCEtLSAgIGRwbHlyOjptdXRhdGUoS05OX3Njb3JlPWlmZWxzZShpcy5uYShLTk5fc2NvcmUpLCAwLCBLTk5fc2NvcmUpLCAtLT4KPCEtLSAgICAgICAgICAgICAgICAgbWV0aG9kPXN0cl9yZW1vdmUobWV0aG9kLCAic2NvcmUuIikpIC0tPgoKCjwhLS0gc2NvcmUuQ0NBLnJlZiA8LSAgIGltYXBfZGJsKGF0YWMubm4ubGlzdCwgfiBzdW0ocHJlZC5jY2EucmVmWy54LDFdID09IHByZWQuY2NhLnJlZlsueSwxXSkvaykgJT4lIHNldE5hbWVzKG5hbWVzKGF0YWMubm4ubGlzdCkpIC0tPgo8IS0tIHNjb3JlLkNvbm9zLnJlZiA8LSBpbWFwX2RibChhdGFjLm5uLmxpc3QsIH4gc3VtKHByZWQuY29ub3MucmVmWy54LDFdID09IHByZWQuY29ub3MucmVmWy55LDFdKS9rKSAlPiUgc2V0TmFtZXMobmFtZXMoYXRhYy5ubi5saXN0KSkgLS0+CjwhLS0gc2NvcmUuTGlnZXIucmVmIDwtIGltYXBfZGJsKGF0YWMubm4ubGlzdCwgfiBzdW0ocHJlZC5saWdlci5yZWZbLngsMV0gPT0gcHJlZC5saWdlci5yZWZbLnksMV0pL2spICU+JSBzZXROYW1lcyhuYW1lcyhhdGFjLm5uLmxpc3QpKSAtLT4KCjwhLS0ga25uX3Njb3JlX3JlZl9kZiA8LSAtLT4KPCEtLSAgIGFzLmRhdGEuZnJhbWUoY2JpbmQoc2NvcmUuQ29ub3MucmVmLCBzY29yZS5MaWdlci5yZWYsIHNjb3JlLkNDQS5yZWYpKSAlPiUgLS0+CjwhLS0gICByb3duYW1lc190b19jb2x1bW4oImNlbGwiKSAlPiUgLS0+CjwhLS0gICBwaXZvdF9sb25nZXIoY29scz1zdHJfc3Vic2V0KGNvbG5hbWVzKC4pLCAic2NvcmUiKSwgbmFtZXNfdG8gPSAibWV0aG9kIiwgdmFsdWVzX3RvID0gIktOTl9zY29yZSIpICU+JSAtLT4KPCEtLSAgIGRwbHlyOjptdXRhdGUoS05OX3Njb3JlPWlmZWxzZShpcy5uYShLTk5fc2NvcmUpLCAwLCBLTk5fc2NvcmUpLCAtLT4KPCEtLSAgICAgICAgICAgICAgICAgbWV0aG9kPXN0cl9yZW1vdmUobWV0aG9kLCAic2NvcmUuIikpIC0tPgoKCjwhLS0gYmluZF9yb3dzKGtubl9zY29yZV9kZiwga25uX3Njb3JlX3JlZl9kZikgJT4lIC0tPgo8IS0tICAgbXV0YXRlKGZlYXR1cmUuc2VsZWN0aW9uPWlmZWxzZShzdHJfZGV0ZWN0KG1ldGhvZCwgInJlZiIpLCAicmVmIiwgInVuaW9uIikpICU+JSAtLT4KPCEtLSAgIG11dGF0ZShtZXRob2Q9c3RyX3JlbW92ZShtZXRob2QsICIucmVmIikpICU+JSAtLT4KPCEtLSAgIGdncGxvdChhZXMoS05OX3Njb3JlLCBjb2xvcj1mZWF0dXJlLnNlbGVjdGlvbiwgZmlsbD1tZXRob2QpKSArIC0tPgo8IS0tICAgc3RhdF9lY2RmKHNpemU9MSkgKyAtLT4KPCEtLSAgIHNjYWxlX2NvbG9yX2JyZXdlcihwYWxldHRlID0gIlNldDEiKSArIC0tPgo8IS0tICAgZmFjZXRfd3JhcChtZXRob2R+LikgKyAtLT4KPCEtLSAgIHRoZW1lX2J3KGJhc2Vfc2l6ZSA9IDE2KSArIC0tPgo8IS0tICAgeWxhYigiRUNERiIpICsgLS0+CjwhLS0gICBnZ3RpdGxlKHBhc3RlKCJLID0iLCBrKSkgKyAtLT4KPCEtLSAgIGdnc2F2ZShwYXN0ZTAob3V0ZGlyLCAidW5pb25WU3JlZmVyZW5jZV9LTk4ucG5nIiksIGhlaWdodCA9IDQsIHdpZHRoID0gMTApIC0tPgoKPCEtLSBgYGAgLS0+Cgo8IS0tIC0tLSAtLT4KCjwhLS0gYGBge3J9IC0tPgo8IS0tIHBsb3RseTo6Z2dwbG90bHkoRGltUGxvdChvcmlnLlJOQS5zZXUsIHJlZHVjdGlvbiA9ICJ1bWFwLnNuYXAiLCBncm91cC5ieSA9ICJwcmVkaWN0ZWQuaWRfQ0NBIikpIC0tPgo8IS0tIHBsb3RseTo6Z2dwbG90bHkoRGltUGxvdChhdGFjLnNldSwgcmVkdWN0aW9uID0gInVtYXAuc25hcCIsIGdyb3VwLmJ5ID0gInByZWRpY3RlZC5pZF9DQ0EiKSkgLS0+CjwhLS0gYGBgIC0tPgoKCiMjIyBUaG91Z2h0cwotIENvbm9zIHNjb3JlcyBhIGxvdCBvZiBjZWxscyB3aXRoIGhpZ2ggY29uZmlkZW5jZSwgYnV0IGZhaWxzIHRvIGFzc2lnbiBjZWxscyB0byBkaWZmaWN1bHQgY2x1c3RlcnMgCi0gQ0NBIHJlc2VtYmxlcyB0aGUgY29tcG9zaXRpb24gb2YgdGhlIFJOQSBkYXRhIGJldHRlciwgYnV0IGN1cmlvdXMgdGhhdCB0aGUgb3RoZXIgbWV0aG9kcyBpZGVudGlmeSB3YXkgbW9yZSAKCgoKCgoKCgoKCg==